--with Ada.Numerics; use Ada.Numerics;
with Ada.Numerics.Discrete_Random; --use Numerics.Discrete_Random;
with Ada.Numerics.Float_Random;
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Fixed;
with Ada.Exceptions;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;

procedure zapateria is
   -----------------------------------------------------------------------
   --	DEFINICION DE TIPOS
   -----------------------------------------------------------------------
   type Estados is (PEDIDO, EN_REPARACION, REPARADO, ENTREGADO);
   type Nombres is (Julian,Hipolito,Benito,Zorba,Repartidor1,Repartidor2);

   type zapato is record
      Id_zapato 	: Integer;
      Nombre 		: Unbounded_String;
      Direccion 	: Unbounded_String;
      Tiempo    	: Integer;
      Estado 		: Estados := PEDIDO;
   end record;

   --type ListaZapatos is array (Positive range <>) of zapato;
   type ListaZapatos is array (0..99) of zapato;

   -- estas tienen implementado el algoritmo RendezVous, ya propio de ADA
   task type Estanteria is
      entry CargarNombre(n: in Character);
      entry agregar(x: in zapato);
      entry quitar(y: out zapato);
   end Estanteria;


   -- tasks para las personas que trabajan en la zapateria. Estos son threads.
   task type Zapatero (pos: Nombres);

   task type Repartidor (pos: Nombres);

   task type Recepcionista (pos: Nombres);

   function ValorRandom return Integer;

   procedure CargaPedidos(lista: out ListaZapatos; cant: out Integer);

   function ZapatoToStr(Z: in zapato) return String;

   -----------------------------------------------------------------------
   --	DEFINICION DE VARIABLES GLOBALES
   -----------------------------------------------------------------------

   Estanteria_A : Estanteria;
   Estanteria_B : Estanteria;
   Julian_task : Recepcionista(pos=>Julian);
   Hipolito_task : Zapatero(pos=>Hipolito);
   Benito_task : Zapatero(pos=>Benito);
   Zorba_task : Zapatero(pos=>Zorba);
   repartidor1_task : Repartidor(pos=>Repartidor1);
   repartidor2_task : Repartidor(pos=>Repartidor2);
   Cant_Estanteria_A: Integer;
   Cant_Estanteria_B: Integer;
   FinCargaPedidos: Boolean := False;

   -----------------------------------------------------------------------
   --	IMPLEMENTANCION DE TASKS
   -----------------------------------------------------------------------

   -- Implementacion de Estanteria
   task body Estanteria is
      cantidad : integer := 0;
      max : integer := 69; -- pueden tomarse hasta 70 pedidos
      buff: array(0..max) of zapato;
      nombre: Character;

   begin
      accept CargarNombre(n: in Character) do
         nombre := n;
      end CargarNombre;

      loop
         select
            when cantidad < max => -- solo se pueden agregar elementos a la estanteria si no se ha superado el maximo del buffer
               accept agregar(x : in zapato) do
                  buff(cantidad) := x;
--                    Put_Line("Estanteria_" & nombre & " - Se ha agregado en la posicion: " & Integer'Image(cantidad) & " el zapato:" & ASCII.LF & ZapatoToStr(x));

                  if (nombre = 'A') then
                     Cant_Estanteria_A := cantidad+1;
                  else
                     Cant_Estanteria_B := cantidad+1;
                  end if;
                  cantidad := cantidad+1;
               end agregar;

         or
            when cantidad > 0 => -- solo se pueden quitar elementos si la cantidad actual es mayor a 0
               accept quitar(y : out zapato) do
                  y := buff(cantidad-1);
--                    Put_Line("Estanteria_" & nombre & " - Se ha quitado de la posicion: " & Integer'Image(cantidad-1) & " el zapato:" & ASCII.LF & ZapatoToStr(y));
	          if (nombre = 'A') then --actualizo las variables globales de cantidad para la estanteria correspondiente
                     Cant_Estanteria_A := cantidad-1;
                  else
                     Cant_Estanteria_B := cantidad-1;
                  end if;
                  cantidad := cantidad-1;
               end quitar;

         else
            delay Duration(ValorRandom); --si no toma ninguna llamada a los 2 servicios, se duerme un tiempo

         end select;
      end loop;
   end Estanteria;

   -----------------------------------------------------------------------
   task body Zapatero is
      Z: zapato;
      SeguirTrabajando: Boolean := True;
   begin

      Ada.Text_IO.Put_Line("Iniciando zapatero " & Nombres'Image(pos));

      while SeguirTrabajando loop

         delay Duration(ValorRandom);

         Estanteria_A.quitar(Z);
         Z.Estado := EN_REPARACION;
         Ada.Text_IO.Put_Line("zapatero " & Nombres'Image(pos) & " tomo de Estanteria_A el zapato: " & ASCII.LF & ZapatoToStr(Z));

         delay Duration(Z.Tiempo);
         Z.Estado := REPARADO;
         Ada.Text_IO.Put_Line("zapatero " & Nombres'Image(pos) & " agregando a Estanteria_B el zapato: " & ASCII.LF & ZapatoToStr(Z) );
         Estanteria_B.agregar(Z);

         delay Duration(ValorRandom);

         if (FinCargaPedidos) and (Cant_Estanteria_A = 0) then
            Ada.Text_IO.Put_Line("Se finalizo la carga de pedidos y la Estanteria_A se encuentra vacia. Se finaliza el zapatero " & Nombres'Image(pos) );
            SeguirTrabajando := False;
         end if;

      end loop;
  end Zapatero;

   -----------------------------------------------------------------------
   task body Recepcionista is
      lista: ListaZapatos;
      n: Integer := 0;
      largo: Integer;
      Z : zapato;
   begin
      Ada.Text_IO.Put_Line("Iniciando recepcionista " & Nombres'Image(pos));

      CargaPedidos(lista,largo);
      while n < largo -- se carga la lista completa de pedidos
      loop

         Ada.Text_IO.Put_Line("recepcionista " & Nombres'Image(pos) & " tomando con pedido numero " & Integer'Image(n) );
         if ((n mod 30) = 0) then
            delay Duration(ValorRandom);
         end if;
         Z := lista(n);
         Z.Tiempo := ValorRandom;
	 Ada.Text_IO.Put_Line("recepcionista " & Nombres'Image(pos) & " agregando en Estanteria_A el zapato: " & ASCII.LF & ZapatoToStr(Z));
         Estanteria_A.agregar(Z);

         n := n+1;
      end loop;
      FinCargaPedidos := True; --Se completo la carga de todos los pedidos del archivo finaliza el trabajo del recepcionista.
      Ada.Text_IO.Put_Line("Se finaliza el recepcionista " & Nombres'Image(pos));

   end Recepcionista;

   -----------------------------------------------------------------------
   task body Repartidor is
      Z: zapato;
      I: Integer := 1;
      SeguirTrabajando: Boolean := True;
   begin
      Ada.Text_IO.Put_Line("Iniciando repartidor " & Nombres'Image(pos));
      while SeguirTrabajando loop
         for I in 1..6 loop -- loop para dormir al repartidor un rato mas que al resto
            delay Duration(ValorRandom);
         end loop;

         Estanteria_B.quitar(Z);
	 Z.Estado := ENTREGADO;
         Ada.Text_IO.Put_Line("repartidor " & Nombres'Image(pos) & " tomo zapato: " & ASCII.LF & ZapatoToStr(Z));
         if (Cant_Estanteria_B = 0) and (Cant_Estanteria_A = 0) then
            Ada.Text_IO.Put_Line("Ambas estanterias se encuentran vacias. Se finaliza al repartidor " & Nombres'Image(pos) );
            SeguirTrabajando := False;
         end if;
      end loop;

   end Repartidor;

   -----------------------------------------------------------------------
   function ValorRandom return Integer is
      type Rand_Range is range 0 .. 5;
      package Rand_Int is new Ada.Numerics.Discrete_Random(Rand_Range);
      seed : Rand_Int.Generator;
      Num : Integer;
   begin
      Rand_Int.Reset(seed);
      Num :=Integer(Rand_Int.Random(seed));
      return Num;

   end ValorRandom;

   -----------------------------------------------------------------------
   procedure CargaPedidos(lista: out ListaZapatos; cant: out Integer) is

      Archivo : Ada.Text_IO.File_Type;
      As_Input : constant Ada.Text_IO.File_Mode := Ada.Text_IO.In_File;
      PathArchivo : String := "pedidos.txt";
      Linea : String(1..256);
      Largo_Linea : Integer;
      Z: zapato;
      n: Integer := 0;
      pos : Integer;
   begin
      Ada.Text_IO.Open(Archivo, As_Input, PathArchivo);
      Ada.Text_IO.Put_Line("Se comienza la carga de pedidos del archivo de configuracion");
      Input_Routine:
      while not Ada.Text_IO.End_Of_File(Archivo)
      loop
         Ada.Text_IO.Get_Line(Archivo, Linea, Largo_Linea);
         Z.Id_zapato := n;
         Ada.Text_IO.Put_Line("Se lee la linea " & Linea(1..Largo_Linea));
         pos := Ada.Strings.Fixed.Index(Linea,";");

         if pos /= 0 then
            Z.Nombre := To_Unbounded_String(Linea(1..pos-1));
--              Ada.Text_IO.Put_Line("Nombre: " & To_String(Z.Nombre));
            Z.Direccion := To_Unbounded_String(Linea(pos+1..Largo_Linea));
--              Ada.Text_IO.Put_Line("Direccion: " & To_String(Z.Direccion));
            lista(n):= Z;
            n := n+1;

         else
            Ada.Text_IO.Put_Line("No se encuentra ';' Ignorando la linea: " & Linea(1..Largo_Linea) );
         end if;

      end loop Input_Routine;
      Ada.Text_IO.Close(Archivo);
      cant := n;
   exception
      when Error: others =>
         Ada.Text_IO.Put_Line("ocurrio una excepcion");
         Ada.Text_IO.Put_Line(Ada.Exceptions.Exception_Information(Error));
   end CargaPedidos;

   -----------------------------------------------------------------------
   function ZapatoToStr(Z: in zapato) return String is
   begin
      return "-----------------" & ASCII.LF & "Id: " & Integer'Image(Z.Id_zapato) & ASCII.LF & "Nombre: " & To_String(Z.Nombre) & ASCII.LF & "Direccion: " & To_String(Z.Direccion) & ASCII.LF & "Estado: " & Estados'Image(Z.Estado) & ASCII.LF & "Tiempo estimado reparacion: " & Integer'Image(Z.Tiempo) & ASCII.LF & "-----------------";
   end ZapatoToStr;

-----------------------------------------------------------------------
--	MAIN DEL PROCEDIMIENTO
-----------------------------------------------------------------------

begin -- main procedure
   Estanteria_A.CargarNombre('A');
   Estanteria_B.CargarNombre('B');
   null;
end zapateria;
